package cn.kgc.controller;

import cn.kgc.base.vo.SecKillSessionAndSecKillSkuVO;
import cn.kgc.base.vo.SecKillSkuInfoVo;
import cn.kgc.base.vo.SecKillSkuVo;
import cn.kgc.client.SeckillPmsFeignClient;
import cn.kgc.commons.exception.HttpException;
import cn.kgc.commons.util.RedisUtil;
import cn.kgc.commons.util.UserContextHolder;
import cn.kgc.commons.vo.Result;
import cn.kgc.constant.SeckillConstant;
import cn.kgc.dto.SpuItemVO;
import cn.kgc.service.SeckillService;
import cn.kgc.vo.PmsSkuInfoAndSeckillSpuInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p></p>
 *
 * @author 李孟鸠
 * @Date: 2023年01月10日9:37
 */
@RestController
@RequestMapping("/seckill")
@Api(tags = "秒杀控制器")
public class SeckillController {

    @Resource
    private SeckillService seckillService;

    @Resource
    private SeckillPmsFeignClient seckillPmsFeignClient;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;

    @GetMapping("/getSecSessionAndSku")
    @ApiOperation(value = "提供现在正在秒杀的场次以及商品信息",
            produces = "application/json;charset=UTF-8",
            httpMethod = "GET", response = Result.class)
    @CrossOrigin
    public List<SecKillSessionAndSecKillSkuVO> getSecSessions() {
        return seckillService.getSkillSessionAndSku();
    }

    @GetMapping("/get")
    @ApiOperation(value = "根据随机码获得商品的信息以及秒杀商品信息", httpMethod = "GET", produces = "application/json;charset=utf-8",
            response = Result.class)
    @ApiImplicitParam(name = "randomCode", value = "秒杀商品的随机码",
            paramType = "query", dataType = "string")
    public PmsSkuInfoAndSeckillSpuInfo get(@RequestParam(name = "randomCode") String randomCode) {
        String pmsCacheKey = SeckillConstant.SECKILL_SESSIONS_CACHE_PREFIX + randomCode;
        SecKillSkuVo secKillSkuVo = (SecKillSkuVo) redisUtil.get(pmsCacheKey);
        Long skuId = secKillSkuVo.getSkuId();
        Result<SpuItemVO> result = seckillPmsFeignClient.detail(skuId);
        SpuItemVO spuItemVO = result.getData();

        //封装结果对象
        PmsSkuInfoAndSeckillSpuInfo pms = PmsSkuInfoAndSeckillSpuInfo.builder()
                .secKillSkuVo(secKillSkuVo).spuItemVO(spuItemVO).build();
        return pms;
    }
    //秒杀商品:传入商品的ID 商品的随机码 秒杀商品的数量 场次ID
    //最后返回一个订单号，生成订单。（订单的信息，订单项的详情信息）
    //问题：
    //1如果商品秒杀成功以后，生成了订单，订单的状态应该时未支付。超时关单，自动释放库存。
    //（1）超时关单的思路：将订单信息放到一个延迟队列中，超时以后将订单放入到死信队列中。
    // 从死信队列中获得订单，判断订单的状态，如果订单的状态是未支付，将订单的状态改为关闭
    //（2）释放库存的思路：将库存的锁定信息放入到一个延迟队列中，超时以后将锁定信息放入到死信队列中
    // 从死信队列中获得信息，判断订单状态是，如果订单的状态是未支付，释放库存。
    //2 秒杀商品要生成订单信息，将订单信息保存到数据库中。使用RabbitMQ,削峰。

    //秒杀商品的流程
    //1 判断时间是否在秒杀的时间中，如果不在范围之内，返回秒杀活动未开始或者已经结束
    //2 用户是否之前抢到过该商品，如果抢到过，不能在重复抢购商品信息。
    //每个用户抢购每个商品时，商品都会有一个限购数量。假设商品每次限购2台。
    //方案1:只要抢购过，就不能在抢购商品了。
    //方案2：可以重复抢购，但是数量加起来不能超过每个人的限购数量。（选中）

    //3 判断库存是否充足，如果库存充足，可以抢购
    //问题：如何防止超卖问题。
    //方案1：上锁，一次只有一个线程减少库存。
    //方案2：信号量（剩余库存）  （选中）


    //4 返回一订单号

    //5 使用消息队列，对生成订单操作削峰

    //RabbitMQ干了什么事
    //延迟操作
    //最终一致性事务
    //削峰
    //接口之间的解耦 秒杀的接口  生成订单的接口


    //返回秒杀商品的信息
    ///(1)秒杀的商品信息从哪里来？
    //数据库中有商品的信息，能不能只返回数据库中的商品信息
    // 从我们的数据库中来，返回的商品的基本信息 :SpuItemVO
    // 从我们的Redis中来，返回的商品的秒杀的相关信息：SecKillSkuVO
    //商品的秒杀的开始时间，商品秒杀的结束时间 什么时候赋值？？秒杀商品上架时赋值。


    ///(2) 如何返回数据，需要创建一个实体类，在实体类中包含商品的基本信息和秒杀的相关信息。
    ///(3)根据什么获得秒杀的商品信息，根据随机码获得商品的信息
    //方案1：
    //根据商品的Id获得商品的秒杀信息，不大符合我们的Redis中保存数据结构
    //Redis中保存的商品的信息的Key的前缀：seckill:sku：商品的ID_场次的ID
    //原来有一个根据商品的ID获得商品的信息接口，改造那个接口即可，带回秒杀的信息即可。
    //方案2：（选择）
    //根据随机码获得商品信息
    //后果:前端可能不答应，获得商品信息时，需要解析两类商品（普通商品，秒杀的商品）
    //又重新提供了一个获得秒杀商品信息的接口。
    //获得一个普通商品信息的接口。


    @GetMapping("/seckill")
    @ApiOperation(value = "秒杀商品", httpMethod = "GET", produces = "application/json;charset=utf-8",
            response = Result.class)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "randomCode", value = "秒杀商品的随机码",
                    paramType = "query", dataType = "string"),
            @ApiImplicitParam(name = "seckillNum", value = "秒杀商品的数量",
                    paramType = "query", dataType = "int")
    })
    public Result<String> seckill(@RequestParam(name = "randomCode") String randomCode,
                                  @RequestParam(name = "seckillNum") int seckillNum) {
        String skuKey = SeckillConstant.SECKILL_SKU_CACHE_PREFIX + randomCode;
        if (!redisUtil.hasKey(skuKey)) {
            throw new HttpException(60001, HttpStatus.INTERNAL_SERVER_ERROR.value());
        }
        SecKillSkuVo secKillSkuVo = (SecKillSkuVo) redisUtil.get(skuKey);
        Date date = new Date();
        //

        if (date.before(secKillSkuVo.getSessionStartTime())) {
            throw new HttpException(60002, HttpStatus.INTERNAL_SERVER_ERROR.value());
        }
        if (date.after(secKillSkuVo.getSessionEndTime())) {
            throw new HttpException(60003, HttpStatus.INTERNAL_SERVER_ERROR.value());
        }
        //是否重复秒杀
        String userId = UserContextHolder.getInstance().getUserId();
        String seckillSkuTokenKey =
                SeckillConstant.SECKILL_SKU_CACHE_PREFIX + randomCode + ":" + userId;
        int limit = secKillSkuVo.getSecKillLimit(); //每个人的限购数量
        if (redisUtil.hasKey(seckillSkuTokenKey)) { //秒杀过
            //获得已经秒杀过的数量
            int seckillCount = (int) redisUtil.get(seckillSkuTokenKey);

            if (seckillCount + seckillNum > limit) {  //如果已经秒杀的数量+这次秒杀的数量>限购数量
                throw new HttpException(60004, HttpStatus.INTERNAL_SERVER_ERROR.value());
            }
        } else {
            //   没有秒杀过
            if (seckillNum > limit) { //这次秒杀的数量>限购数量
                throw new HttpException(60004,
                        HttpStatus.INTERNAL_SERVER_ERROR.value());
            }
        }
        //没有秒杀过的情况下或者秒杀过商品但是没有超过限购数量
        //库存是否充足，如果库存充足的情况下，减少库存（信号量）
        String semaphoreKey = SeckillConstant.SECKILL_SKU_SEMAPHORE + randomCode;
        RSemaphore rSemaphore = redissonClient.getSemaphore(semaphoreKey);
        if (rSemaphore.tryAcquire(seckillNum)) {
            //秒杀成功
            String orderSn = UUID.randomUUID().toString();
            //记录用户秒杀的信息，第一种情况是已经没有秒杀过的  第二种情况是以前秒过
            long times1 = secKillSkuVo.getSessionStartTime().getTime();
            long times2 = secKillSkuVo.getSessionEndTime().getTime();
            int seckillCount;
            if (redisUtil.hasKey(seckillSkuTokenKey)) {
                seckillCount = (Integer) redisUtil.get(seckillSkuTokenKey);
                seckillCount = seckillCount + seckillNum;
            } else {
                seckillCount = seckillNum;
            }
            redisUtil.set(seckillSkuTokenKey, seckillCount, times2 - times1,
                    TimeUnit.MINUTES);
            //todo 保存订单 用rabbitMq
            return Result.ok(orderSn, "");
        } else {
            throw new HttpException(60005, HttpStatus.INSUFFICIENT_STORAGE.value());
        }
    }

}
